#pragma once

#include "Layer.h"
#include "IPv4Layer.h"
#ifdef _MSC_VER
#	include <winsock2.h>
#else
#	include <sys/time.h>
#endif
#include <vector>

/// @file

/// @namespace pcpp
/// @brief The main namespace for the PcapPlusPlus lib
namespace pcpp
{

	/// @struct icmphdr
	/// Represents ICMP basic protocol header (common for all ICMP message types)
#pragma pack(push, 1)
	typedef struct icmphdr
	{
		/// message type
		uint8_t type;
		/// message code
		uint8_t code;
		/// message checksum
		uint16_t checksum;
	} icmphdr;
#pragma pack(pop)
	static_assert(sizeof(icmphdr) == 4, "icmphdr size is not 4 bytes");

	/// An enum of all supported ICMP message types
	enum IcmpMessageType
	{
		/// ICMP echo (ping) reply message
		ICMP_ECHO_REPLY = 0,
		/// ICMP destination unreachable message
		ICMP_DEST_UNREACHABLE = 3,
		/// ICMP source quench message
		ICMP_SOURCE_QUENCH = 4,
		/// ICMP redirect message
		ICMP_REDIRECT = 5,
		/// ICMP echo (ping) request message
		ICMP_ECHO_REQUEST = 8,
		/// ICMP router advertisement message
		ICMP_ROUTER_ADV = 9,
		/// ICMP router soliciatation message
		ICMP_ROUTER_SOL = 10,
		/// ICMP time-to-live excceded message
		ICMP_TIME_EXCEEDED = 11,
		/// ICMP parameter problem message
		ICMP_PARAM_PROBLEM = 12,
		/// ICMP timestamp request message
		ICMP_TIMESTAMP_REQUEST = 13,
		/// ICMP timestamp reply message
		ICMP_TIMESTAMP_REPLY = 14,
		/// ICMP information request message
		ICMP_INFO_REQUEST = 15,
		/// ICMP information reply message
		ICMP_INFO_REPLY = 16,
		/// ICMP address mask request message
		ICMP_ADDRESS_MASK_REQUEST = 17,
		/// ICMP address mask reply message
		ICMP_ADDRESS_MASK_REPLY = 18,
		/// ICMP message type unsupported by PcapPlusPlus
		ICMP_UNSUPPORTED = 255
	};

	/// An enum for all possible codes for a destination unreachable message type
	/// Documentation is taken from Wikipedia: https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol
	enum IcmpDestUnreachableCodes
	{
		/// Network unreachable error
		IcmpNetworkUnreachable = 0,
		/// Host unreachable error
		IcmpHostUnreachable = 1,
		/// Protocol unreachable error (the designated transport protocol is not supported)
		IcmpProtocolUnreachable = 2,
		/// Port unreachable error (the designated protocol is unable to inform the host of the incoming message)
		IcmpPortUnreachable = 3,
		/// The datagram is too big. Packet fragmentation is required but the 'don't fragment' (DF) flag is on
		IcmpDatagramTooBig = 4,
		/// Source route failed error
		IcmpSourceRouteFailed = 5,
		/// Destination network unknown error
		IcmpDestinationNetworkUnknown = 6,
		/// Destination host unknown error
		IcmpDestinationHostUnknown = 7,
		/// Source host isolated error
		IcmpSourceHostIsolated = 8,
		/// The destination network is administratively prohibited
		IcmpDestinationNetworkProhibited = 9,
		/// The destination host is administratively prohibited
		IcmpDestinationHostProhibited = 10,
		/// The network is unreachable for Type Of Service
		IcmpNetworkUnreachableForTypeOfService = 11,
		/// The host is unreachable for Type Of Service
		IcmpHostUnreachableForTypeOfService = 12,
		/// Communication administratively prohibited (administrative filtering prevents
		/// packet from being forwarded)
		IcmpCommunicationProhibited = 13,
		/// Host precedence violation (indicates the requested precedence is not permitted for
		/// the combination of host or network and port)
		IcmpHostPrecedenceViolation = 14,
		/// Precedence cutoff in effect (precedence of datagram is below the level set by
		/// the network administrators)
		IcmpPrecedenceCutoff = 15
	};

	/// @struct icmp_echo_hdr
	/// ICMP echo (ping) request/reply message structure
#pragma pack(push, 1)
	typedef struct icmp_echo_hdr : icmphdr
	{
		/// the echo (ping) request identifier
		uint16_t id;
		/// the echo (ping) request sequence number
		uint16_t sequence;
		/// a timestamp of when the message was sent
		uint64_t timestamp;
	} icmp_echo_hdr;
#pragma pack(pop)
	static_assert(sizeof(icmp_echo_hdr) == 16, "icmp_echo_hdr size is not 16 bytes");

	/// @struct icmp_echo_request
	/// ICMP echo (ping) request/reply message structure
	typedef struct icmp_echo_request
	{
		/// a pointer to the header data
		icmp_echo_hdr* header;
		/// most echo requests/replies contain some payload data. This is the data length
		size_t dataLength;
		/// most echo requests/replies contain some payload data. This is a pointer to this data
		uint8_t* data;
	} icmp_echo_request;

	/// @typedef icmp_echo_reply
	/// ICMP echo (ping) reply message structure, same as icmp_echo_request
	typedef icmp_echo_request icmp_echo_reply;

	/// @struct icmp_timestamp_request
	/// ICMP timestamp request message structure
#pragma pack(push, 1)
	typedef struct icmp_timestamp_request : icmphdr
	{
		/// the timestamp request identifier
		uint16_t id;
		/// the timestamp request sequence number
		uint16_t sequence;
		/// the time (in milliseconds since midnight) the sender last touched the packet
		uint32_t originateTimestamp;
		/// relevant for timestamp reply only - the time the echoer first touched it on receipt
		uint32_t receiveTimestamp;
		/// relevant for timestamp reply only - the time the echoer last touched the message on sending it
		uint32_t transmitTimestamp;
	} icmp_timestamp_request;
#pragma pack(pop)
	static_assert(sizeof(icmp_timestamp_request) == 20, "icmp_timestamp_request size is not 20 bytes");

	/// @typedef icmp_timestamp_reply
	/// ICMP timestamp reply message structure, same as icmp_timestamp_request
	typedef icmp_timestamp_request icmp_timestamp_reply;
	static_assert(sizeof(icmp_timestamp_reply) == 20, "icmp_timestamp_reply size is not 20 bytes");

	/// @struct icmp_destination_unreachable
	/// ICMP destination unreachable message structure
#pragma pack(push, 1)
	typedef struct icmp_destination_unreachable : icmphdr
	{
		/// unused 2 bytes
		uint16_t unused;
		/// contains the MTU of the next-hop network if a code 4 error occurs
		uint16_t nextHopMTU;
	} icmp_destination_unreachable;
#pragma pack(pop)
	static_assert(sizeof(icmp_destination_unreachable) == 8, "icmp_destination_unreachable size is not 8 bytes");

	/// @struct icmp_time_exceeded
	/// ICMP time-to-live exceeded message structure
#pragma pack(push, 1)
	typedef struct icmp_time_exceeded : icmphdr
	{
		/// unused 4 bytes
		uint32_t unused;
	} icmp_time_exceeded;
#pragma pack(pop)
	static_assert(sizeof(icmp_time_exceeded) == 8, "icmp_time_exceeded size is not 8 bytes");

	/// @typedef icmp_source_quench
	/// ICMP source quence message structure, same as icmp_time_exceeded
	typedef icmp_time_exceeded icmp_source_quench;
	static_assert(sizeof(icmp_source_quench) == 8, "icmp_source_quench size is not 8 bytes");

	/// @struct icmp_param_problem
	/// ICMP parameter problem message structure
#pragma pack(push, 1)
	typedef struct icmp_param_problem : icmphdr
	{
		/// in the case of an invalid IP header (Code 0), this field indicates the byte offset of the error in the
		/// header
		uint8_t pointer;
		/// unused 1 byte
		uint8_t unused1;
		/// unused 2 bytes
		uint16_t unused2;
	} icmp_param_problem;
#pragma pack(pop)
	static_assert(sizeof(icmp_param_problem) == 8, "icmp_param_problem size is not 8 bytes");

	/// @typedef icmp_router_solicitation
	/// ICMP router solicitation message structure, same as icmphdr
	typedef icmphdr icmp_router_solicitation;
	static_assert(sizeof(icmp_router_solicitation) == 4, "icmp_router_solicitation size is not 4 bytes");

	/// @struct icmp_redirect
	/// ICMP redirect message structure
#pragma pack(push, 1)
	typedef struct icmp_redirect : icmphdr
	{
		/// an IPv4 address of the gateway to which the redirection should be sent
		uint32_t gatewayAddress;
	} icmp_redirect;
#pragma pack(pop)
	static_assert(sizeof(icmp_redirect) == 8, "icmp_redirect size is not 8 bytes");

	/// @struct icmp_router_address_structure
	/// Router address structure, relevant for ICMP router advertisement message type (icmp_router_advertisement)
#pragma pack(push, 1)
	struct icmp_router_address_structure
	{
		/// the IPv4 address of the advertised router
		uint32_t routerAddress;
		/// The preferability of the router address as a default router address, relative to other router addresses
		/// on the same subnet. This is a twos-complement value where higher values indicate that the route is
		/// more preferable
		uint32_t preferenceLevel;

		/// Set router address structure from a given IPv4 address and preference level
		/// @param[in] addr IPv4 address to set
		/// @param[in] preference Preference level to set
		void setRouterAddress(IPv4Address addr, uint32_t preference);

		/// @return The IPv4 address extracted from icmp_router_address_structure#routerAddress field
		IPv4Address getAddress() const
		{
			return routerAddress;
		}
	};
#pragma pack(pop)
	static_assert(sizeof(icmp_router_address_structure) == 8, "icmp_router_address_structure size is not 8 bytes");

	/// @struct icmp_router_advertisement_hdr
	/// ICMP router advertisement message structure
#pragma pack(push, 1)
	typedef struct icmp_router_advertisement_hdr : icmphdr
	{
		/// the number of router advertisements in this message. Each advertisement contains one router
		/// address/preference level pair
		uint8_t advertisementCount;
		/// the number of 32-bit words of information for each router address entry in the list. The value is normally
		/// set to 2 (router address + preference level)
		uint8_t addressEntrySize;
		/// the maximum number of seconds that the router addresses in this list may be considered valid
		uint16_t lifetime;
	} icmp_router_advertisement_hdr;
#pragma pack(pop)
	static_assert(sizeof(icmp_router_advertisement_hdr) == 8, "icmp_router_advertisement_hdr size is not 8 bytes");

	/// @struct icmp_router_advertisement
	/// ICMP router advertisement message structure
	struct icmp_router_advertisement
	{
		/// a pointer to the header data on the packet
		icmp_router_advertisement_hdr* header;

		/// Extract router advertisement at a given index
		/// @param[in] index The index of the router advertisement
		/// @return A pointer to the router advertisement on the packet or null if index is out of range (less than zero
		/// or greater than the number of router advertisement records on this message, determined by advertisementCount
		/// field)
		icmp_router_address_structure* getRouterAddress(int index) const;
	};

	/// @struct icmp_address_mask_request
	/// ICMP address mask request message structure
#pragma pack(push, 1)
	typedef struct icmp_address_mask_request : icmphdr
	{
		/// the address mask request identifier
		uint16_t id;
		/// the address mask request sequence
		uint16_t sequence;
		/// the subnet mask of the requesting host
		uint32_t addressMask;
	} icmp_address_mask_request;
#pragma pack(pop)
	static_assert(sizeof(icmp_address_mask_request) == 12, "icmp_address_mask_request size is not 12 bytes");

	/// @typedef icmp_address_mask_reply
	/// ICMP address mask reply message structure, same as icmp_address_mask_request
	typedef icmp_address_mask_request icmp_address_mask_reply;

	/// @struct icmp_info_request
	/// ICMP information request message structure
#pragma pack(push, 1)
	typedef struct icmp_info_request : icmphdr
	{
		/// the information request identifier
		uint16_t id;
		/// the information request sequence
		uint16_t sequence;
	} icmp_info_request;
#pragma pack(pop)
	static_assert(sizeof(icmp_info_request) == 8, "icmp_info_request size is not 8 bytes");

	/// @typedef icmp_info_reply
	/// ICMP information reply message structure, same as icmp_info_request
	typedef icmp_info_request icmp_info_reply;
	static_assert(sizeof(icmp_info_reply) == 8, "icmp_info_reply size is not 8 bytes");

	/// @class IcmpLayer
	/// Represents an ICMP protocol layer (for IPv4 only)
	class IcmpLayer : public Layer
	{
	private:
		icmp_echo_request m_EchoData;
		mutable icmp_router_advertisement m_RouterAdvData;

		bool cleanIcmpLayer();

		bool setEchoData(IcmpMessageType echoType, uint16_t id, uint16_t sequence, uint64_t timestamp,
		                 const uint8_t* data, size_t dataLen);

		bool setIpAndL4Layers(IPv4Layer* ipLayer, Layer* l4Layer);

	public:
		/// A constructor that creates the layer from an existing packet raw data
		/// @param[in] data A pointer to the raw data (will be casted to @ref arphdr)
		/// @param[in] dataLen Size of the data in bytes
		/// @param[in] prevLayer A pointer to the previous layer
		/// @param[in] packet A pointer to the Packet instance where layer will be stored in
		// cppcheck-suppress uninitMemberVar
		IcmpLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet)
		    : Layer(data, dataLen, prevLayer, packet, ICMP)
		{}

		/// An empty constructor that creates a new layer with an empty ICMP header without setting the ICMP type or
		/// ICMP data. Call the set*Data() methods to set ICMP type and data
		IcmpLayer();

		~IcmpLayer() override = default;

		/// Get a pointer to the basic ICMP header. Notice this points directly to the data, so every change will change
		/// the actual packet data
		/// @return A pointer to the @ref icmphdr
		icmphdr* getIcmpHeader() const
		{
			return reinterpret_cast<icmphdr*>(m_Data);
		}

		/// @return The ICMP message type
		IcmpMessageType getMessageType() const;

		/// @param[in] type Type to check
		/// @return True if the layer if of the given type, false otherwise
		bool isMessageOfType(IcmpMessageType type) const
		{
			return getMessageType() == type;
		}

		/// @return ICMP echo (ping) request data. If the layer isn't of type ICMP echo request nullptr is returned
		icmp_echo_request* getEchoRequestData();

		/// Set echo (ping) request message data
		/// @param[in] id Echo (ping) request identifier
		/// @param[in] sequence Echo (ping) request sequence
		/// @param[in] timestamp Echo (ping) request timestamp
		/// @param[in] data A pointer to echo (ping) request payload to set
		/// @param[in] dataLen The length of the echo (ping) request payload
		/// @return A pointer to the echo (ping) request data that have been set or nullptr if something went wrong
		/// (an appropriate error log is printed in such cases)
		icmp_echo_request* setEchoRequestData(uint16_t id, uint16_t sequence, uint64_t timestamp, const uint8_t* data,
		                                      size_t dataLen);

		/// @return ICMP echo reply data. If the layer isn't of type ICMP echo reply nullptr is returned
		icmp_echo_reply* getEchoReplyData();

		/// Set echo (ping) reply message data
		/// @param[in] id Echo (ping) reply identifier
		/// @param[in] sequence Echo (ping) reply sequence
		/// @param[in] timestamp Echo (ping) reply timestamp
		/// @param[in] data A pointer to echo (ping) reply payload to set
		/// @param[in] dataLen The length of the echo (ping) reply payload
		/// @return A pointer to the echo (ping) reply data that have been set or nullptr if something went wrong
		/// (an appropriate error log is printed in such cases)
		icmp_echo_reply* setEchoReplyData(uint16_t id, uint16_t sequence, uint64_t timestamp, const uint8_t* data,
		                                  size_t dataLen);

		/// @return ICMP timestamp request data. If the layer isn't of type ICMP timestamp request nullptr is returned
		icmp_timestamp_request* getTimestampRequestData();

		/// Set timestamp request message data
		/// @param[in] id Timestamp request identifier
		/// @param[in] sequence Timestamp request sequence
		/// @param[in] originateTimestamp Time (in milliseconds since midnight) the sender last touched the packet
		/// @return A pointer to the timestamp request data that have been set or nullptr if something went wrong
		/// (an appropriate error log is printed in such cases)
		icmp_timestamp_request* setTimestampRequestData(uint16_t id, uint16_t sequence, timeval originateTimestamp);

		/// @return ICMP timestamp reply data. If the layer isn't of type ICMP timestamp reply nullptr is returned
		icmp_timestamp_reply* getTimestampReplyData();

		/// Set timestamp reply message data
		/// @param[in] id Timestamp reply identifier
		/// @param[in] sequence Timestamp reply sequence
		/// @param[in] originateTimestamp Time (in milliseconds since midnight) the sender last touched the packet
		/// @param[in] receiveTimestamp The time the echoer first touched it on receipt
		/// @param[in] transmitTimestamp The time the echoer last touched the message on sending it
		/// @return A pointer to the timestamp reply data that have been set or nullptr if something went wrong
		/// (an appropriate error log is printed in such cases)
		icmp_timestamp_reply* setTimestampReplyData(uint16_t id, uint16_t sequence, timeval originateTimestamp,
		                                            timeval receiveTimestamp, timeval transmitTimestamp);

		/// @return ICMP destination unreachable data. If the layer isn't of type ICMP destination unreachable nullptr
		/// is returned. The IP and L4 (ICMP/TCP/UDP) headers of the destination unreachable data are parsed as separate
		/// layers and can be retrieved via this->getNextLayer()
		icmp_destination_unreachable* getDestUnreachableData();

		/// Set destination unreachable message data. This method only works if IcmpLayer is already part of a packet
		/// (not a standalone layer). The reason is the Internet and L4 headers given as parameters are added as
		/// separate layers and need a packet to be added to
		/// @param[in] code Destination unreachable code
		/// @param[in] nextHopMTU The MTU of the next-hop network if a code 4 error occurs
		/// @param[in] ipHeader The Internet header of the original data. This layer is added as a separate layer on the
		/// packet
		/// @param[in] l4Header The L4 header of the original data. This layer is added as a separate layer on the
		/// packet
		/// @return A pointer to the destination unreachable data that have been set or nullptr if something went wrong
		/// (an appropriate error log is printed in such cases)
		icmp_destination_unreachable* setDestUnreachableData(IcmpDestUnreachableCodes code, uint16_t nextHopMTU,
		                                                     IPv4Layer* ipHeader, Layer* l4Header);

		/// @return ICMP source quench data. If the layer isn't of type ICMP source quench nullptr is returned.
		/// The IP and L4 (ICMP/TCP/UDP) headers of the source quench data are parsed as separate layers and can be
		/// retrieved via this->getNextLayer()
		icmp_source_quench* getSourceQuenchdata();

		/// Set source quench message data. This method only works if IcmpLayer is already part of a packet (not
		/// a standalone layer). The reason is the Internet and L4 headers given as parameters are added as separate
		/// layers and need a packet to be added to
		/// @param[in] ipHeader The Internet header of the original data. This layer is added as a separate layer on the
		/// packet
		/// @param[in] l4Header The L4 header of the original data. This layer is added as a separate layer on the
		/// packet
		/// @return A pointer to the source quench data that have been set or nullptr if something went wrong
		/// (an appropriate error log is printed in such cases)
		icmp_source_quench* setSourceQuenchdata(IPv4Layer* ipHeader, Layer* l4Header);

		/// @return ICMP redirect data. If the layer isn't of type ICMP redirect nullptr is returned.
		/// The IP and L4 (ICMP/TCP/UDP) headers of the redirect data are parsed as separate layers and can be
		/// retrieved via this->getNextLayer()
		icmp_redirect* getRedirectData();

		/// Set redirect message data. This method only works if IcmpLayer is already part of a packet (not
		/// a standalone layer). The reason is the Internet and L4 headers given as parameters are added as separate
		/// layers and need a packet to be added to
		/// @param[in] code The redirect message code. Only values between 0 and 3 are legal, the rest will cause the
		/// method to fail
		/// @param[in] gatewayAddress An IPv4 address of the gateway to which the redirection should be sent
		/// @param[in] ipHeader The Internet header of the original data. This layer is added as a separate layer on the
		/// packet
		/// @param[in] l4Header The L4 header of the original data. This layer is added as a separate layer on the
		/// packet
		/// @return A pointer to the redirect data that have been set or nullptr if something went wrong
		/// (an appropriate error log is printed in such cases)
		icmp_redirect* setRedirectData(uint8_t code, IPv4Address gatewayAddress, IPv4Layer* ipHeader, Layer* l4Header);

		/// @return ICMP router advertisement data. If the layer isn't of type ICMP router advertisement nullptr is
		/// returned
		icmp_router_advertisement* getRouterAdvertisementData() const;

		/// Set router advertisement message data
		/// @param[in] code The router advertisement message code. Only codes 0 or 16 are legal, the rest will fail the
		/// method
		/// @param[in] lifetimeInSeconds The maximum number of seconds that the router addresses in this list may be
		/// considered valid
		/// @param[in] routerAddresses A vector of router advertisements to set
		/// @return A pointer to the router advertisement data that have been set or nullptr if something went wrong
		/// (an appropriate error log is printed in such cases)
		icmp_router_advertisement* setRouterAdvertisementData(
		    uint8_t code, uint16_t lifetimeInSeconds,
		    const std::vector<icmp_router_address_structure>& routerAddresses);

		/// @return ICMP router solicitation data. If the layer isn't of type ICMP router solicitation nullptr is
		/// returned
		icmp_router_solicitation* getRouterSolicitationData();

		/// Set router solicitation message data. This message accepts no parameters as there are no parameters to this
		/// type of message (code is always zero)
		/// @return A pointer to the router solicitation data that have been set or nullptr if something went wrong
		/// (an appropriate error log is printed in such cases)
		icmp_router_solicitation* setRouterSolicitationData();

		/// @return ICMP time-to-live exceeded data. If the layer isn't of type ICMP time-to-live exceeded nullptr is
		/// returned. The IP and L4 (ICMP/TCP/UDP) headers of the time exceeded data are parsed as separate layers and
		/// can be retrieved via this->getNextLayer()
		icmp_time_exceeded* getTimeExceededData();

		/// Set time-to-live exceeded message data. This method only works if IcmpLayer is already part of a packet (not
		/// a standalone layer). The reason is the Internet and L4 headers given as parameters are added as separate
		/// layers and need a packet to be added to
		/// @param[in] code Time-to-live exceeded message code. Only codes 0 or 1 are legal, the rest will fail the
		/// method
		/// @param[in] ipHeader The Internet header of the original data. This layer is added as a separate layer on the
		/// packet
		/// @param[in] l4Header The L4 header of the original data. This layer is added as a separate layer on the
		/// packet
		/// @return A pointer to the time-to-live exceeded data that have been set or nullptr if something went wrong
		/// (an appropriate error log is printed in such cases)
		icmp_time_exceeded* setTimeExceededData(uint8_t code, IPv4Layer* ipHeader, Layer* l4Header);

		/// @return ICMP parameter problem data. If the layer isn't of type ICMP parameter problem nullptr is returned
		icmp_param_problem* getParamProblemData();

		/// Set parameter problem message data. This method only works if IcmpLayer is already part of a packet (not
		/// a standalone layer). The reason is the Internet and L4 headers given as parameters are added as separate
		/// layers and need a packet to be added to
		/// @param[in] code Parameter problem message code. Only code between 0 and 2 are legal, the rest will fail the
		/// method
		/// @param[in] errorOctetPointer In the case of an invalid IP header (Code 0), indicate the byte offset of the
		/// error in the header
		/// @param[in] ipHeader The Internet header of the original data. This layer is added as a separate layer on the
		/// packet
		/// @param[in] l4Header The L4 header of the original data. This layer is added as a separate layer on the
		/// packet
		/// @return A pointer to the parameter problem data that have been set or nullptr if something went wrong
		/// (an appropriate error log is printed in such cases)
		icmp_param_problem* setParamProblemData(uint8_t code, uint8_t errorOctetPointer, IPv4Layer* ipHeader,
		                                        Layer* l4Header);

		/// @return ICMP address mask request data. If the layer isn't of type ICMP address mask request nullptr is
		/// returned
		icmp_address_mask_request* getAddressMaskRequestData();

		/// Set address mask request message data
		/// @param[in] id Address mask request identifier
		/// @param[in] sequence Address mask request sequence
		/// @param[in] mask The subnet mask of the requesting host
		/// @return A pointer to the address mask request data that have been set or nullptr if something went wrong
		/// (an appropriate error log is printed in such cases)
		icmp_address_mask_request* setAddressMaskRequestData(uint16_t id, uint16_t sequence, IPv4Address mask);

		/// @return ICMP address mask reply data. If the layer isn't of type ICMP address mask reply nullptr is returned
		icmp_address_mask_reply* getAddressMaskReplyData();

		/// Set address mask reply message data
		/// @param[in] id Address mask reply identifier
		/// @param[in] sequence Address mask reply sequence
		/// @param[in] mask The subnet mask of the requesting host
		/// @return A pointer to the address mask reply data that have been set or nullptr if something went wrong
		/// (an appropriate error log is printed in such cases)
		icmp_address_mask_reply* setAddressMaskReplyData(uint16_t id, uint16_t sequence, IPv4Address mask);

		/// @return ICMP address information request data. If the layer isn't of type ICMP information request nullptr
		/// is returned
		icmp_info_request* getInfoRequestData();

		/// Set information request message data
		/// @param[in] id Information request identifier
		/// @param[in] sequence Information request sequence
		/// @return A pointer to the information request data that have been set or nullptr if something went wrong
		/// (an appropriate error log is printed in such cases)
		icmp_info_request* setInfoRequestData(uint16_t id, uint16_t sequence);

		/// @return ICMP address information reply data. If the layer isn't of type ICMP information reply nullptr is
		/// returned
		icmp_info_reply* getInfoReplyData();

		/// Set information reply message data
		/// @param[in] id Information reply identifier
		/// @param[in] sequence Information reply sequence
		/// @return A pointer to the information reply data that have been set or nullptr if something went wrong
		/// (an appropriate error log is printed in such cases)
		icmp_info_reply* setInfoReplyData(uint16_t id, uint16_t sequence);

		/// The static method makes validation of input data
		/// @param[in] data The pointer to the beginning of byte stream of ICMP packet
		/// @param[in] dataLen The length of byte stream
		/// @return True if the data is valid and can represent an ICMP packet
		static inline bool isDataValid(const uint8_t* data, size_t dataLen);

		// implement abstract methods

		/// ICMP messages of types: ICMP_DEST_UNREACHABLE, ICMP_SOURCE_QUENCH, ICMP_TIME_EXCEEDED, ICMP_REDIRECT,
		/// ICMP_PARAM_PROBLEM have data that contains IPv4 header and some L4 header (TCP/UDP/ICMP). This method parses
		/// these headers as separate layers on top of the ICMP layer
		void parseNextLayer() override;

		/// @return The ICMP header length. This length varies according to the ICMP message type. This length doesn't
		/// include IPv4 and L4 headers in case ICMP message type are: ICMP_DEST_UNREACHABLE, ICMP_SOURCE_QUENCH,
		/// ICMP_TIME_EXCEEDED, ICMP_REDIRECT, ICMP_PARAM_PROBLEM
		size_t getHeaderLen() const override;

		/// Calculate ICMP checksum field
		void computeCalculateFields() override;

		std::string toString() const override;

		OsiModelLayer getOsiModelLayer() const override
		{
			return OsiModelNetworkLayer;
		}
	};

	// implementation of inline methods

	bool IcmpLayer::isDataValid(const uint8_t* data, size_t dataLen)
	{
		if (dataLen < sizeof(icmphdr))
			return false;

		uint8_t type = data[0];

		// ICMP_ECHO_REQUEST, ICMP_ECHO_REPLY, ICMP_ROUTER_SOL, ICMP_INFO_REQUEST, ICMP_INFO_REPLY
		if (type == 8 || type == 0 || type == 10 || type == 15 || type == 16)
			return true;

		// ICMP_TIMESTAMP_REQUEST, ICMP_TIMESTAMP_REPLY
		if (type == 13 || type == 14)
			return dataLen >= sizeof(icmp_timestamp_request);

		// ICMP_ADDRESS_MASK_REPLY, ICMP_ADDRESS_MASK_REQUEST
		if (type == 17 || type == 18)
			return dataLen >= sizeof(icmp_address_mask_request);

		// ICMP_DEST_UNREACHABLE
		if (type == 3)
			return dataLen >= sizeof(icmp_destination_unreachable);

		// ICMP_REDIRECT
		if (type == 5)
			return dataLen >= sizeof(icmp_redirect);

		// ICMP_TIME_EXCEEDED, ICMP_SOURCE_QUENCH
		if (type == 4 || type == 11)
			return dataLen >= sizeof(icmp_time_exceeded);

		// ICMP_PARAM_PROBLEM
		if (type == 12)
			return dataLen >= sizeof(icmp_param_problem);

		// ICMP_ROUTER_ADV
		if (type == 9)
			return dataLen >= sizeof(icmp_router_advertisement_hdr);

		return false;
	}

}  // namespace pcpp
